In [177]:
import graphlab

Read product review


In [178]:
products = graphlab.SFrame('amazon_baby.gl/')

Exploration


In [179]:
products.head(2)


Out[179]:
name review rating
Planetwise Flannel Wipes These flannel wipes are
OK, but in my opinion ...
3.0
Planetwise Wipe Pouch it came early and was not
disappointed. i love ...
5.0
[2 rows x 3 columns]

Build word count vector for each review


In [180]:
products['word_count'] = graphlab.text_analytics.count_words(products['review'])

In [181]:
products.head(2)


Out[181]:
name review rating word_count
Planetwise Flannel Wipes These flannel wipes are
OK, but in my opinion ...
3.0 {'and': 5, '6': 1,
'stink': 1, 'because' ...
Planetwise Wipe Pouch it came early and was not
disappointed. i love ...
5.0 {'and': 3, 'love': 1,
'it': 2, 'highly': 1, ...
[2 rows x 4 columns]


In [182]:
graphlab.canvas.set_target('ipynb')

In [183]:
products['name'].show()


Explore Vulli Sophie


In [184]:
giraffe_reviews = products[products['name']=='Vulli Sophie the Giraffe Teether']

In [185]:
giraffe_reviews.head(2)
len(giraffe_reviews)


Out[185]:
785

In [186]:
giraffe_reviews['rating'].show(view='Categorical')


Build a sentiment classifier


In [187]:
products['rating'].show(view='Categorical')


Define +ve and -ve sentiments

Ignore 3 start reviews


In [188]:
products = products[products['rating']!=3]

In [189]:
products['sentiment'] = products['rating']>=4

In [190]:
products.head()


Out[190]:
name review rating word_count sentiment
Planetwise Wipe Pouch it came early and was not
disappointed. i love ...
5.0 {'and': 3, 'love': 1,
'it': 2, 'highly': 1, ...
1
Annas Dream Full Quilt
with 2 Shams ...
Very soft and comfortable
and warmer than it ...
5.0 {'and': 2, 'quilt': 1,
'it': 1, 'comfortable': ...
1
Stop Pacifier Sucking
without tears with ...
This is a product well
worth the purchase. I ...
5.0 {'ingenious': 1, 'and':
3, 'love': 2, ...
1
Stop Pacifier Sucking
without tears with ...
All of my kids have cried
non-stop when I tried to ...
5.0 {'and': 2, 'parents!!':
1, 'all': 2, 'puppet.': ...
1
Stop Pacifier Sucking
without tears with ...
When the Binky Fairy came
to our house, we didn't ...
5.0 {'and': 2, 'this': 2,
'her': 1, 'help': 2, ...
1
A Tale of Baby's Days
with Peter Rabbit ...
Lovely book, it's bound
tightly so you may no ...
4.0 {'shop': 1, 'noble': 1,
'is': 1, 'it': 1, 'as': ...
1
Baby Tracker® - Daily
Childcare Journal, ...
Perfect for new parents.
We were able to keep ...
5.0 {'and': 2, 'all': 1,
'right': 1, 'when': 1, ...
1
Baby Tracker® - Daily
Childcare Journal, ...
A friend of mine pinned
this product on Pinte ...
5.0 {'and': 1, 'help': 1,
'give': 1, 'is': 1, ' ...
1
Baby Tracker® - Daily
Childcare Journal, ...
This has been an easy way
for my nanny to record ...
4.0 {'journal.': 1, 'nanny':
1, 'standarad': 1, ...
1
Baby Tracker® - Daily
Childcare Journal, ...
I love this journal and
our nanny uses it ...
4.0 {'all': 1, 'forget': 1,
'just': 1, 'food': 1, ...
1
[10 rows x 5 columns]

Train sentiment classifier


In [191]:
train_data, test_data = products.random_split(0.8, seed=0)

In [192]:
len(train_data)
len(test_data)


Out[192]:
33304

In [193]:
sentiment_model = graphlab.logistic_classifier.create(train_data,
                                                      target='sentiment',
                                                      features=['word_count'],
                                                      validation_set=test_data)


PROGRESS: Logistic regression:
PROGRESS: --------------------------------------------------------
PROGRESS: Number of examples          : 133448
PROGRESS: Number of classes           : 2
PROGRESS: Number of feature columns   : 1
PROGRESS: Number of unpacked features : 219217
PROGRESS: Number of coefficients    : 219218
PROGRESS: Starting L-BFGS
PROGRESS: --------------------------------------------------------
PROGRESS: +-----------+----------+-----------+--------------+-------------------+---------------------+
PROGRESS: | Iteration | Passes   | Step size | Elapsed Time | Training-accuracy | Validation-accuracy |
PROGRESS: +-----------+----------+-----------+--------------+-------------------+---------------------+
PROGRESS: | 1         | 5        | 0.000002  | 2.040783     | 0.841481          | 0.839989            |
PROGRESS: | 2         | 9        | 3.000000  | 4.176414     | 0.947425          | 0.894877            |
PROGRESS: | 3         | 10       | 3.000000  | 4.976086     | 0.923768          | 0.866232            |
PROGRESS: | 4         | 11       | 3.000000  | 5.882759     | 0.971779          | 0.912743            |
PROGRESS: | 5         | 12       | 3.000000  | 6.709300     | 0.975511          | 0.908900            |
PROGRESS: | 6         | 13       | 3.000000  | 7.551617     | 0.899991          | 0.825967            |
PROGRESS: | 7         | 15       | 1.000000  | 8.829425     | 0.984548          | 0.921451            |
PROGRESS: | 8         | 16       | 1.000000  | 9.663256     | 0.985118          | 0.921871            |
PROGRESS: | 9         | 17       | 1.000000  | 10.519770    | 0.987066          | 0.919709            |
PROGRESS: | 10        | 18       | 1.000000  | 11.439875    | 0.988715          | 0.916256            |
PROGRESS: +-----------+----------+-----------+--------------+-------------------+---------------------+

Evaluate model


In [194]:
sentiment_model.evaluate(test_data, metric='roc_curve')


Out[194]:
{'roc_curve': Columns:
 	threshold	float
 	fpr	float
 	tpr	float
 	p	int
 	n	int
 
 Rows: 1001
 
 Data:
 +------------------+----------------+------------------+-------+------+
 |    threshold     |      fpr       |       tpr        |   p   |  n   |
 +------------------+----------------+------------------+-------+------+
 |       0.0        | 0.218579234973 | 0.00553294781181 | 28014 | 5307 |
 | 0.0010000000475  | 0.781420765027 |  0.994467052188  | 28014 | 5307 |
 | 0.00200000009499 | 0.742792538157 |  0.993003498251  | 28014 | 5307 |
 | 0.00300000002608 | 0.721123045035 |  0.992003998001  | 28014 | 5307 |
 | 0.00400000018999 | 0.705483323912 |  0.991361462126  | 28014 | 5307 |
 | 0.00499999988824 | 0.694554362163 |  0.990861712001  | 28014 | 5307 |
 | 0.00600000005215 | 0.684944413039 |  0.990290569001  | 28014 | 5307 |
 | 0.00700000021607 | 0.675146033541 |  0.989648033126  | 28014 | 5307 |
 | 0.00800000037998 | 0.665159223667 |  0.989326765189  | 28014 | 5307 |
 | 0.00899999961257 | 0.658187299793 |  0.988862711501  | 28014 | 5307 |
 +------------------+----------------+------------------+-------+------+
 [1001 rows x 5 columns]
 Note: Only the head of the SFrame is printed.
 You can use print_rows(num_rows=m, num_columns=n) to print more rows and columns.}

In [195]:
sentiment_model.show(view='Evaluation')


Apply model to Giraffe


In [196]:
giraffe_reviews['predicted_sentiment'] = sentiment_model.predict(giraffe_reviews, output_type='probability')

In [197]:
giraffe_reviews.head(2)


Out[197]:
name review rating word_count predicted_sentiment
Vulli Sophie the Giraffe
Teether ...
He likes chewing on all
the parts especially the ...
5.0 {'and': 1, 'all': 1,
'because': 1, 'it': 1, ...
0.999513023521
Vulli Sophie the Giraffe
Teether ...
My son loves this toy and
fits great in the diaper ...
5.0 {'and': 1, 'right': 1,
'help': 1, 'just': 1, ...
0.999320678306
[2 rows x 5 columns]

Sort reviews by predicted sentiment


In [198]:
giraffe_reviews = giraffe_reviews.sort('predicted_sentiment', ascending=False)

In [199]:
giraffe_reviews.head(2)


Out[199]:
name review rating word_count predicted_sentiment
Vulli Sophie the Giraffe
Teether ...
Sophie, oh Sophie, your
time has come. My ...
5.0 {'giggles': 1, 'all': 1,
"violet's": 2, 'bring': ...
1.0
Vulli Sophie the Giraffe
Teether ...
I'm not sure why Sophie
is such a hit with the ...
4.0 {'adoring': 1, 'find': 1,
'month': 1, 'bright': 1, ...
0.999999999703
[2 rows x 5 columns]


In [200]:
giraffe_reviews[1]['review']


Out[200]:
"I'm not sure why Sophie is such a hit with the little ones, but my 7 month old baby girl is one of her adoring fans.  The rubber is softer and more pleasant to handle, and my daughter has enjoyed chewing on her legs and the nubs on her head even before she started teething.  She also loves the squeak that Sophie makes when you squeeze her.  Not sure what it is but if Sophie is amongst a pile of her other toys, my daughter will more often than not reach for Sophie.  And I have the peace of mind of knowing that only edible and safe paints and materials have been used to make Sophie, as opposed to Bright Starts and other baby toys made in China.  Now that the research is out on phthalates and other toxic substances in baby toys, I think it's more important than ever to find good quality toys that are also safe for our babies to handle and put in their mouths.  Sophie is a must-have for every new mom in my opinion.  Even if your kid is one of the few that can take or leave her, it's worth a try.  Vulli, the makers of Sophie, also make natural rubber teething rings that my daughter loves as well."

In [201]:
giraffe_reviews[-2]['review']


Out[201]:
"This children's toy is nostalgic and very cute. However, there is a distinct rubber smell and a very odd taste, yes I tried it, that my baby did not enjoy. Also, if it is soiled it is extremely difficult to clean as the rubber is a kind of porus material and does not clean well. The final thing is the squeaking device inside which stopped working after the first couple of days. I returned this item feeling I had overpaid for a toy that was defective and did not meet my expectations. Please do not be swayed by the cute packaging and hype surounding it as I was. One more thing, I was given a full refund from Amazon without any problem."

Assignment


In [202]:
selected_words = ['awesome', 'great', 'fantastic', 'amazing', 'love',
                  'horrible', 'bad', 'terrible', 'awful', 'wow', 'hate']

In [203]:
def awesome_count(dictionary):
    if 'awesome' in dictionary:
        return dictionary['awesome']
    else:
        return 0

def great_count(dictionary):
    if 'great' in dictionary:
        return dictionary['great']
    else:
        return 0

def fantastic_count(dictionary):
    if 'fantastic' in dictionary:
        return dictionary['fantastic']
    else:
        return 0

def amazing_count(dictionary):
    if 'amazing' in dictionary:
        return dictionary['amazing']
    else:
        return 0

def love_count(dictionary):
    if 'love' in dictionary:
        return dictionary['love']
    else:
        return 0

def horrible_count(dictionary):
    if 'horrible' in dictionary:
        return dictionary['horrible']
    else:
        return 0

def bad_count(dictionary):
    if 'bad' in dictionary:
        return dictionary['bad']
    else:
        return 0

def terrible_count(dictionary):
    if 'terrible' in dictionary:
        return dictionary['terrible']
    else:
        return 0

def awful_count(dictionary):
    if 'awful' in dictionary:
        return dictionary['awful']
    else:
        return 0

def wow_count(dictionary):
    if 'wow' in dictionary:
        return dictionary['wow']
    else:
        return 0

def hate_count(dictionary):
    if 'hate' in dictionary:
        return dictionary['hate']
    else:
        return 0

In [204]:
products_copy = products
products['awesome'] = products['word_count'].apply(awesome_count)
products['great'] = products['word_count'].apply(great_count)
products['fantastic'] = products['word_count'].apply(fantastic_count)
products['amazing'] = products['word_count'].apply(amazing_count)
products['love'] = products['word_count'].apply(love_count)
products['horrible'] = products['word_count'].apply(horrible_count)
products['bad'] = products['word_count'].apply(bad_count)
products['terrible'] = products['word_count'].apply(terrible_count)
products['awful'] = products['word_count'].apply(awful_count)
products['wow'] = products['word_count'].apply(wow_count)
products['hate'] = products['word_count'].apply(hate_count)

In [205]:
print sum(products['awesome'])
print sum(products['great'])
print sum(products['fantastic'])
print sum(products['amazing'])
print sum(products['love'])
print sum(products['horrible'])
print sum(products['bad'])
print sum(products['terrible'])
print sum(products['awful'])
print sum(products['wow'])
print sum(products['hate'])


2002
42420
873
1305
40277
659
3197
673
345
131
1057

In [206]:
train_data, test_data = products.random_split(0.8, seed=0)

In [207]:
selected_words_model = graphlab.logistic_classifier.create(train_data,
                                                      target='sentiment',
                                                      features=selected_words,
                                                      validation_set=test_data)


PROGRESS: Logistic regression:
PROGRESS: --------------------------------------------------------
PROGRESS: Number of examples          : 133448
PROGRESS: Number of classes           : 2
PROGRESS: Number of feature columns   : 11
PROGRESS: Number of unpacked features : 11
PROGRESS: Number of coefficients    : 12
PROGRESS: Starting Newton Method
PROGRESS: --------------------------------------------------------
PROGRESS: +-----------+----------+--------------+-------------------+---------------------+
PROGRESS: | Iteration | Passes   | Elapsed Time | Training-accuracy | Validation-accuracy |
PROGRESS: +-----------+----------+--------------+-------------------+---------------------+
PROGRESS: | 1         | 2        | 0.319980     | 0.844299          | 0.842842            |
PROGRESS: | 2         | 3        | 0.521981     | 0.844186          | 0.842842            |
PROGRESS: | 3         | 4        | 0.720257     | 0.844276          | 0.843142            |
PROGRESS: | 4         | 5        | 0.943112     | 0.844269          | 0.843142            |
PROGRESS: | 5         | 6        | 1.152977     | 0.844269          | 0.843142            |
PROGRESS: | 6         | 7        | 1.356862     | 0.844269          | 0.843142            |
PROGRESS: +-----------+----------+--------------+-------------------+---------------------+

In [208]:
#selected_words_model['coefficients']
print selected_words_model['coefficients']['name']
print selected_words_model['coefficients']['value']


['(intercept)', 'awesome', 'great', 'fantastic', 'amazing', 'love', 'horrible', 'bad', 'terrible', 'awful', 'wow', 'hate']
[1.3672831522930413, 1.0580088887848118, 0.8839378948979686, 0.8913030903043073, 0.8928024225084743, 1.399898343017463, -1.9965180055878033, -0.9858273699287603, -2.090499984872607, -1.7646995563132173, -0.05414501233325174, -1.409164062756384]

In [209]:
selected_words_model.evaluate(test_data)


Out[209]:
{'accuracy': 0.8431419649291376, 'confusion_matrix': Columns:
 	target_label	int
 	predicted_label	int
 	count	int
 
 Rows: 4
 
 Data:
 +--------------+-----------------+-------+
 | target_label | predicted_label | count |
 +--------------+-----------------+-------+
 |      0       |        0        |  234  |
 |      0       |        1        |  5094 |
 |      1       |        1        | 27846 |
 |      1       |        0        |  130  |
 +--------------+-----------------+-------+
 [4 rows x 3 columns]}

In [210]:
sentiment_model.evaluate(test_data)


Out[210]:
{'accuracy': 0.916256305548883, 'confusion_matrix': Columns:
 	target_label	int
 	predicted_label	int
 	count	int
 
 Rows: 4
 
 Data:
 +--------------+-----------------+-------+
 | target_label | predicted_label | count |
 +--------------+-----------------+-------+
 |      0       |        1        |  1328 |
 |      0       |        0        |  4000 |
 |      1       |        1        | 26515 |
 |      1       |        0        |  1461 |
 +--------------+-----------------+-------+
 [4 rows x 3 columns]}

In [211]:
len(test_data[test_data['sentiment']==1])


Out[211]:
27976

In [212]:
27976.0/33304


Out[212]:
0.8400192169108815

In [217]:
diaper_champ_reviews_all = products_copy[products_copy['name']=='Baby Trend Diaper Champ']

In [219]:
diaper_champ_reviews_all.head(2)


Out[219]:
name review rating word_count sentiment awesome
Baby Trend Diaper Champ Ok - newsflash. Diapers
are just smelly. We've ...
4.0 {'son': 1, 'just': 2,
'less': 1, '-': 3, ...
1 0
Baby Trend Diaper Champ My husband and I selected
the Diaper "Champ" ma ...
1.0 {'material)': 1, 'bags,':
1, 'less': 1, 'when': 3, ...
0 0
great fantastic amazing love horrible bad terrible awful wow hate
0 0 0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0 0 0
[2 rows x 16 columns]


In [220]:
diaper_champ_reviews = products[products['name']=='Baby Trend Diaper Champ']

In [221]:
diaper_champ_reviews.head(2)


Out[221]:
name review rating word_count sentiment awesome
Baby Trend Diaper Champ Ok - newsflash. Diapers
are just smelly. We've ...
4.0 {'son': 1, 'just': 2,
'less': 1, '-': 3, ...
1 0
Baby Trend Diaper Champ My husband and I selected
the Diaper "Champ" ma ...
1.0 {'material)': 1, 'bags,':
1, 'less': 1, 'when': 3, ...
0 0
great fantastic amazing love horrible bad terrible awful wow hate
0 0 0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0 0 0
[2 rows x 16 columns]


In [222]:
diaper_champ_reviews['predicted_sentiment1'] = sentiment_model.predict(diaper_champ_reviews,
                                                                      output_type='probability')

In [223]:
diaper_champ_reviews.sort('predicted_sentiment1', ascending=False)


Out[223]:
name review rating word_count sentiment awesome
Baby Trend Diaper Champ Baby Luke can turn a
clean diaper to a dirty ...
5.0 {'all': 1, 'less': 1,
"friend's": 1, '(which': ...
1 0
Baby Trend Diaper Champ I LOOOVE this diaper
pail! Its the easies ...
5.0 {'just': 1, 'over': 1,
'rweek': 1, 'sooo': 1, ...
1 0
Baby Trend Diaper Champ We researched all of the
different types of di ...
4.0 {'all': 2, 'just': 4,
"don't": 2, 'one,': 1, ...
1 0
Baby Trend Diaper Champ My baby is now 8 months
and the can has been ...
5.0 {"don't": 1, 'able': 2,
'over': 1, 'soon': 1, ...
1 0
Baby Trend Diaper Champ This is absolutely, by
far, the best diaper ...
5.0 {'just': 3, 'money': 1,
'still': 3, 'fine': 1, ...
1 0
Baby Trend Diaper Champ Diaper Champ or Diaper
Genie? That was my ...
5.0 {'son': 2, 'all': 1,
'bags.': 1, 'son,': 1, ...
1 0
Baby Trend Diaper Champ Wow! This is fabulous.
It was a toss-up between ...
5.0 {'and': 4, 'this': 3,
'stink': 1, 'garbage' ...
1 0
Baby Trend Diaper Champ I originally put this
item on my baby registry ...
5.0 {'lysol': 1, 'all': 2,
'bags.': 1, 'feedback': ...
1 0
Baby Trend Diaper Champ Two girlfriends and two
family members put me ...
5.0 {'just': 1, '-': 3,
'both': 1, 'results': 1, ...
1 0
Baby Trend Diaper Champ I am one of those super-
critical shoppers who ...
5.0 {'all': 1, 'humid': 1,
'just': 1, 'less': 1, ...
1 0
great fantastic amazing love horrible bad terrible awful wow hate predicted_sentiment1
0 0 0 0 0 0 0 0 0 0 0.999999937267
0 0 0 1 0 0 0 0 0 0 0.999999917406
0 0 0 0 0 1 0 0 0 0 0.999999899509
2 0 0 0 0 1 0 0 0 0 0.999999836182
0 0 0 2 0 0 0 0 0 0 0.999999824745
0 0 0 0 0 0 0 0 0 0 0.999999759315
0 0 0 0 0 0 0 0 0 0 0.999999692111
0 0 0 0 0 0 0 0 0 0 0.999999642488
0 0 0 0 1 0 0 0 0 0 0.999999604504
0 0 0 1 0 0 0 0 0 0 0.999999486804
[298 rows x 17 columns]
Note: Only the head of the SFrame is printed.
You can use print_rows(num_rows=m, num_columns=n) to print more rows and columns.


In [224]:
diaper_champ_reviews['predicted_sentiment2'] = selected_words_model.predict(diaper_champ_reviews[selected_words], output_type='probability')

In [225]:
diaper_champ_reviews.sort('predicted_sentiment1', ascending=False).head(15)


Out[225]:
name review rating word_count sentiment awesome
Baby Trend Diaper Champ Baby Luke can turn a
clean diaper to a dirty ...
5.0 {'all': 1, 'less': 1,
"friend's": 1, '(which': ...
1 0
Baby Trend Diaper Champ I LOOOVE this diaper
pail! Its the easies ...
5.0 {'just': 1, 'over': 1,
'rweek': 1, 'sooo': 1, ...
1 0
Baby Trend Diaper Champ We researched all of the
different types of di ...
4.0 {'all': 2, 'just': 4,
"don't": 2, 'one,': 1, ...
1 0
Baby Trend Diaper Champ My baby is now 8 months
and the can has been ...
5.0 {"don't": 1, 'able': 2,
'over': 1, 'soon': 1, ...
1 0
Baby Trend Diaper Champ This is absolutely, by
far, the best diaper ...
5.0 {'just': 3, 'money': 1,
'still': 3, 'fine': 1, ...
1 0
Baby Trend Diaper Champ Diaper Champ or Diaper
Genie? That was my ...
5.0 {'son': 2, 'all': 1,
'bags.': 1, 'son,': 1, ...
1 0
Baby Trend Diaper Champ Wow! This is fabulous.
It was a toss-up between ...
5.0 {'and': 4, 'this': 3,
'stink': 1, 'garbage' ...
1 0
Baby Trend Diaper Champ I originally put this
item on my baby registry ...
5.0 {'lysol': 1, 'all': 2,
'bags.': 1, 'feedback': ...
1 0
Baby Trend Diaper Champ Two girlfriends and two
family members put me ...
5.0 {'just': 1, '-': 3,
'both': 1, 'results': 1, ...
1 0
Baby Trend Diaper Champ I am one of those super-
critical shoppers who ...
5.0 {'all': 1, 'humid': 1,
'just': 1, 'less': 1, ...
1 0
great fantastic amazing love horrible bad terrible awful wow hate predicted_sentiment1
0 0 0 0 0 0 0 0 0 0 0.999999937267
0 0 0 1 0 0 0 0 0 0 0.999999917406
0 0 0 0 0 1 0 0 0 0 0.999999899509
2 0 0 0 0 1 0 0 0 0 0.999999836182
0 0 0 2 0 0 0 0 0 0 0.999999824745
0 0 0 0 0 0 0 0 0 0 0.999999759315
0 0 0 0 0 0 0 0 0 0 0.999999692111
0 0 0 0 0 0 0 0 0 0 0.999999642488
0 0 0 0 1 0 0 0 0 0 0.999999604504
0 0 0 1 0 0 0 0 0 0 0.999999486804
predicted_sentiment2
0.796940851291
0.940876393428
0.5942241719
0.895606298305
0.984739056527
0.796940851291
0.796940851291
0.796940851291
0.347684052736
0.940876393428
[15 rows x 18 columns]
Note: Only the head of the SFrame is printed.
You can use print_rows(num_rows=m, num_columns=n) to print more rows and columns.


In [226]:
diaper_champ_reviews[0]['review']


Out[226]:
"Ok - newsflash.  Diapers are just smelly.  We've had this pail for 2.5 years now.  It was our first and primary one.  There were no major smell problems until after one year, when our son started eating solids.  Also, we change the bag twice weekly as 3 days is about the max for smell-containment.  Around 20-22 months we started shopping for a container that would be less smelly and didn't find one as good.  (We have a cheaper one upstairs which broke immediately and always stunk!)  We finally just put the Diaper Champ in the attic a few months ago and use the cheap one with the flip-up lid - mainly since the cheapo fits inside the cabinet and we didn't notice a big difference in smell-control.  (The most helpful action is to tie the dirty diapers inside a small plastic bag before putting them in the pail.)A couple of our friends have this pail and were pleased until the children started eating solid food and things got stinkier - but that's pretty much the consensus according to many reviews on the different pails.  I've knocked off a star since the mechanism isn't that intuitive (to grandmothers and babysitters, at least) and it frequently gets stuck when the bag starts getting full.  Plus there are some smell issues, as with any pail.  However, the fact that you can use regular trash bags is so much more convenient!"

In [ ]: